# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

from typing import List, Union

from coin.base.param_util import to_list
from coin.exchange.base.kr_rest.product import NormProduct
from coin.exchange.base.kr_rest.futures_product import NormFuturesProduct

# Binance
import coin.exchange.binance.config as binance_config
from coin.exchange.binance.kr_rest.product import BinanceProduct
from coin.exchange.binance.ws.handler import BinanceHandler
from coin.exchange.binance.ws.snapshot_handler import BinanceSnapshotHandler

# Binance futures
import coin.exchange.binance_futures.ws.config as binance_futures_config
from coin.exchange.binance_futures.kr_rest.futures_product import BinanceFuturesProduct
from coin.exchange.binance_futures.ws.handler import BinanceFuturesHandler

# Bitfinex
import coin.exchange.bitfinex_v2.config as bitfinex_config
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct
from coin.exchange.bitfinex_v2.ws.handler import BitfinexV2Handler

# Bitflyer
import coin.exchange.bitflyer_v1.config as bitflyer_config
import coin.exchange.bitflyer_v1.util as bitflyer_v1_util
from coin.exchange.bitflyer_v1.kr_rest.futures_product import (BitflyerFuturesProduct)
from coin.exchange.bitflyer_v1.ws.handler import BitflyerHandler
from coin.exchange.bitflyer_v1.ws.multiple_book_handler import \
    BitflyerMultipleBookHandler

# Bitmex
from coin.exchange.bitmex.ws.handler import BitmexHandler
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct
from coin.exchange.bitmex.ws.multiple_book_handler import \
    BitmexMultipleBookHandler

# Huobi
import coin.exchange.huobi.config as huobi_config
from coin.exchange.huobi.kr_rest.product import HuobiProduct
from coin.exchange.huobi.ws.handler import HuobiHandler

# Huobi futures
from coin.exchange.huobi_futures.kr_rest.futures_product import HuobiFuturesProduct
from coin.exchange.huobi_futures.ws.handler import HuobiFuturesHandler

# Okex
import coin.exchange.okex.config as okex_config
from coin.exchange.okex.kr_rest.product import OkexProduct
from coin.exchange.okex.ws.handler import OkexHandler
from coin.exchange.okex_v3.ws.handler import OkexHandler as OkexV3Handler

# Okex futures
from coin.exchange.okex_futures.kr_rest.futures_product import OkexFuturesProduct
from coin.exchange.okex_futures.kr_rest.currency import OkexCurrency
from coin.exchange.okex_futures.ws.handler import OkexFuturesHandler
from coin.exchange.okex_futures_swap.ws.handler import OkexFuturesSwapHandler
from coin.exchange.okex_futures_v3.ws.handler import OkexFuturesHandler as OkexFuturesV3Handler

# Gdax
from coin.exchange.gdax.kr_rest.product import GdaxProduct
from coin.exchange.gdax.ws.handler import GdaxHandler
import coin.exchange.gdax.config as gdax_config

# Bithumb
import coin.exchange.bithumb.ws.config as bithumb_config
from coin.exchange.bithumb.kr_rest.product import BithumbProduct
from coin.exchange.bithumb.ws.handler import BithumbHandler
from coin.exchange.bithumb.ws.handler_v2 import BithumbV2Handler
from coin.exchange.bithumb.ws.handler_v3 import BithumbV3Handler

# Upbit
import coin.exchange.upbit_v1.ws_reverse_eng.config as upbit_config
from coin.exchange.upbit_v1.kr_rest.product import UpbitProduct
from coin.exchange.upbit_v1.ws_official.handler import UpbitHandler

# Kraken
import coin.exchange.kraken.config as kraken_config
from coin.exchange.kraken.kr_rest.product import KrakenProduct
from coin.exchange.kraken.rest.handler import KrakenRestHandler
from coin.exchange.kraken.ws.handler import KrakenWsHandler

# Kraken Futures
import coin.exchange.kraken_futures.config as kraken_futures_config
from coin.exchange.kraken_futures.kr_rest.futures_product import KrakenFuturesProduct
from coin.exchange.kraken_futures.ws.handler import KrakenFuturesHandler

# Fcoin
import coin.exchange.fcoin.config as fcoin_config
from coin.exchange.fcoin.kr_rest.product import FcoinProduct
from coin.exchange.fcoin.ws.handler import FcoinHandler

# Quoinex
import coin.exchange.quoinex_v2.config as quoinex_config
from coin.exchange.quoinex_v2.kr_rest.product import QuoinexProduct
from coin.exchange.quoinex_v2.ws.handler import QuoinexHandler

# Hitbtc
import coin.exchange.hitbtc_v2.config as hitbtc_config
from coin.exchange.hitbtc_v2.kr_rest.product import HitbtcProduct
from coin.exchange.hitbtc_v2.ws.handler import HitbtcHandler

# Bequant
import coin.exchange.bequant_v2.config as bequant_config
from coin.exchange.bequant_v2.kr_rest.product import BequantProduct
from coin.exchange.bequant_v2.ws.handler import BequantHandler

# Bitstamp
import coin.exchange.bitstamp_v2.config as bitstamp_config
from coin.exchange.bitstamp_v2.kr_rest.product import BitstampProduct
from coin.exchange.bitstamp_v2.ws.handler import BitstampHandler

# Korbit
import coin.exchange.korbit_v1.config as korbit_config
from coin.exchange.korbit_v1.kr_rest.product import KorbitProduct
from coin.exchange.korbit_v1.rest.handler import KorbitRestHandler
from coin.exchange.korbit_v1.ws.handler import KorbitHandler as KorbitWsHandler

# Coinone
import coin.exchange.coinone_v2.ws.config as coinone_config
from coin.exchange.coinone_v2.kr_rest.product import CoinoneProduct
from coin.exchange.coinone_v2.rest.handler import CoinoneRestHandler
from coin.exchange.coinone_v2.ws.handler import CoinoneHandler

# Deribit
import coin.exchange.deribit_v2.ws.config as deribit_config
from coin.exchange.deribit_v1.kr_rest.futures_product import DeribitFuturesProduct
from coin.exchange.deribit_v2.ws.handler import DeribitHandler

# Gopax
import coin.exchange.gopax.ws.config as gopax_config
from coin.exchange.gopax.kr_rest.product import GopaxProduct
from coin.exchange.gopax.ws.handler import GopaxHandler

# Bybit
import coin.exchange.bybit.ws.config as bybit_config
from coin.exchange.bybit.kr_rest.futures_product import BybitFuturesProduct
from coin.exchange.bybit.ws.handler import BybitHandler

# UniswapV3
import coin.exchange.uniswap_v3.config as uniswapv3_config
from coin.exchange.uniswap_v3.kr_rest.product import UniswapProduct
from coin.exchange.uniswap_v3.rest.handler import UniswapHandler

# UniswapV3Arbitrum
import coin.exchange.uniswap_v3_arbitrum.config as uniswapv3_arbitrum_config

# UniswapV2
import coin.exchange.uniswap_v2.config as uniswapv2_config
from coin.exchange.uniswap_v2.kr_rest.product import UniswapProduct as UniswapV2Product
from coin.exchange.uniswap_v2.rest.handler import UniswapHandler as UniswapV2Handler

from coin.exchange.base.kr_rest.py_product import PyProduct


def _get_feed_group(product, feed_group_finder):
  if product is None:
    groups = ['*']
  else:
    product = to_list(product)
    groups = sorted(set([feed_group_finder(p) for p in product]))
  return groups


class FlowBookBuilderBase:
  ProductType = None
  FeedHandlerType = None
  support_book_builder_observer = False

  def __init__(self, *, feed_checker_config=None, **kwargs):
    self._feed_handler = self.FeedHandlerType(feed_checker_config=feed_checker_config, **kwargs)

  def set_live(self, is_live):
    self._feed_handler.set_live(is_live)

  def observe_book_builder(self, observer):
    assert self.support_book_builder_observer
    self._feed_handler.observe_book_builder(observer)

  def subscribe_event(self, event_type, key, callback):
    attr = 'subscribe_%s' % event_type
    subscribe_func = getattr(self, attr)
    subscribe_func(key, callback)

  # For backward-compatibility.
  def subscribe(self, product, callback):
    self.subscribe_event('book', product, callback)

  def subscribe_feed_status(self, key, callback):
    self._feed_handler.subscribe_feed_status(callback)

  def start_periodic_check(self, ioloop, period):
    self._feed_handler.start_periodic_check(ioloop, period)

  def subscribe_book(self, product, callback):
    if not isinstance(product, self.__class__.ProductType):
      native_symbol = self.__class__.ProductType.FromProduct(product).native_symbol
    else:
      native_symbol = product.native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    if not isinstance(product, self.__class__.ProductType):
      native_symbol = self.__class__.ProductType.FromProduct(product).native_symbol
    else:
      native_symbol = product.native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# Binance
# ----------------------------
class BinanceFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BinanceProduct
  FeedHandlerType = BinanceHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BinanceProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in binance_config.group_symbol.items():
      native_symbol_list = [
          BinanceProduct.FromStr(symbol).native_symbol for symbol in native_symbol_list
      ]
      if native_symbol in native_symbol_list:
        return group
    raise KeyError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['binance_ws_depth_%s%s' % (g, worker_id), 'binance_ws_trade_%s%s' % (g, worker_id)]
    return topics + ['binance_rest_depth_%s' % worker_id]

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('binance_rest_depth_*%s' % worker_id, self._feed_handler.on_rest_book)
    flow_sub.subscribe('binance_ws_depth_*%s' % worker_id, self._feed_handler.on_ws_book)
    flow_sub.subscribe('binance_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Binance snapshot_l1_realtime
# ----------------------------
class BinanceSnapshotL1RealtimeFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BinanceProduct
  FeedHandlerType = BinanceHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BinanceProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in binance_config.group_symbol.items():
      native_symbol_list = [
          BinanceProduct.FromStr(symbol).native_symbol for symbol in native_symbol_list
      ]
      if native_symbol in native_symbol_list:
        return group
    raise KeyError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'binance_ws_book_ticker_%s%s' % (g, worker_id), 'binance_ws_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('binance_ws_book_ticker_*%s' % worker_id, self._feed_handler.on_l1_book)
    flow_sub.subscribe('binance_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Binance snapshot
# ----------------------------
class BinanceSnapshotFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BinanceProduct
  FeedHandlerType = BinanceSnapshotHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BinanceProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in binance_config.group_symbol.items():
      native_symbol_list = [
          BinanceProduct.FromStr(symbol).native_symbol for symbol in native_symbol_list
      ]
      if native_symbol in native_symbol_list:
        return group
    raise KeyError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'binance_ws_depth20_%s%s' % (g, worker_id), 'binance_ws_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('binance_ws_depth20_*%s' % worker_id, self._feed_handler.on_ws_book)
    flow_sub.subscribe('binance_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Binance legacy 1s update
# ----------------------------
class Binance1sFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BinanceProduct
  FeedHandlerType = BinanceHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BinanceProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in binance_config.group_symbol.items():
      native_symbol_list = [
          BinanceProduct.FromStr(symbol).native_symbol for symbol in native_symbol_list
      ]
      if native_symbol in native_symbol_list:
        return group
    raise KeyError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'binance_ws_depth_1s_%s%s' % (g, worker_id), 'binance_ws_trade_%s%s' % (g, worker_id)
      ]
    return topics + ['binance_rest_depth_%s' % worker_id]

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('binance_rest_depth_*%s' % worker_id, self._feed_handler.on_rest_book)
    flow_sub.subscribe('binance_ws_depth_1s_*%s' % worker_id, self._feed_handler.on_ws_book)
    flow_sub.subscribe('binance_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Binance legacy 1s snapshot
# ----------------------------
class BinanceSnapshot1sFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BinanceProduct
  FeedHandlerType = BinanceSnapshotHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BinanceProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in binance_config.group_symbol.items():
      native_symbol_list = [
          BinanceProduct.FromStr(symbol).native_symbol for symbol in native_symbol_list
      ]
      if native_symbol in native_symbol_list:
        return group
    raise KeyError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'binance_ws_depth20_1s_%s%s' % (g, worker_id), 'binance_ws_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('binance_ws_depth20_1s_*%s' % worker_id, self._feed_handler.on_ws_book)
    flow_sub.subscribe('binance_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Binance futures
# ----------------------------
class BinanceFuturesFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BinanceFuturesProduct
  FeedHandlerType = BinanceFuturesHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    symbol = BinanceFuturesProduct.FromProduct(product).symbol
    for group, symbol_list in binance_futures_config.group_symbol.items():
      if symbol in symbol_list:
        return group
    raise KeyError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'binance_ws_futures_depth_%s%s' % (g, worker_id),
          'binance_ws_futures_trade_%s%s' % (g, worker_id)
      ]
    return topics + ['binance_rest_futures_depth_%s' % worker_id]

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('binance_rest_futures_depth_*%s' % worker_id,
                       self._feed_handler.on_rest_book)
    flow_sub.subscribe('binance_ws_futures_depth_*%s' % worker_id, self._feed_handler.on_ws_book)
    flow_sub.subscribe('binance_ws_futures_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Binance futures snapshot_l1_realtime
# ----------------------------
class BinanceFuturesSnapshotL1RealtimeFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BinanceFuturesProduct
  FeedHandlerType = BinanceFuturesHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    symbol = BinanceFuturesProduct.FromProduct(product).symbol
    for group, symbol_list in binance_futures_config.group_symbol.items():
      if symbol in symbol_list:
        return group
    raise KeyError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'binance_ws_futures_book_ticker_%s%s' % (g, worker_id),
          'binance_ws_futures_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('binance_ws_futures_book_ticker_*%s' % worker_id,
                       self._feed_handler.on_l1_book)
    flow_sub.subscribe('binance_ws_futures_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Bitfinex
# ----------------------------
class BitfinexFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BitfinexProduct
  FeedHandlerType = BitfinexV2Handler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_subscription_symbol = BitfinexProduct.FromProduct(product).native_subscription_symbol
    for group, native_subscription_symbol_list in bitfinex_config.group_symbol.items():
      if native_subscription_symbol in native_subscription_symbol_list:
        return group
    raise ValueError(native_subscription_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'bitfinex_v2_ws_book_%s%s' % (g, worker_id),
          'bitfinex_v2_ws_trades_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('bitfinex_v2_ws_book_*%s' % worker_id, self._feed_handler.on_ws_book)
    flow_sub.subscribe('bitfinex_v2_ws_trades_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Bitflyer
# ----------------------------
class BitflyerFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BitflyerFuturesProduct
  FeedHandlerType = BitflyerHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormFuturesProduct):
    return bitflyer_v1_util.find_feed_group(product)

  @classmethod
  def get_topic_string(cls,
                       product: Union[NormFuturesProduct, List[NormFuturesProduct], None],
                       worker_id='1'):
    return bitflyer_v1_util.get_topic_string(product=product, worker_id=worker_id)

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    self._flow_sub = flow_sub
    flow_sub.subscribe('bitflyer_ws_ticker_board_*%s' % worker_id, self._feed_handler.on_message)

  def subscribe_book(self, product, callback):
    native_symbol = bitflyer_v1_util.get_native_symbol(product)
    self._feed_handler.subscribe_book(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    native_symbol = bitflyer_v1_util.get_native_symbol(product)
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_aggregated_book(self, product, aggregated_ticksizes, callback, worker_id='1'):
    self._multiple_handler = BitflyerMultipleBookHandler(aggregated_ticksizes)

    self._flow_sub.subscribe('bitflyer_ws_ticker_board_*%s' % worker_id,
                             self._multiple_handler.on_message)
    native_symbol = BitflyerFuturesProduct.FromProduct(product).native_symbol
    self._multiple_handler.subscribe_book(native_symbol, callback)


# ----------------------------
# Bitmex
# ----------------------------
class BitmexFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BitmexFuturesProduct
  FeedHandlerType = BitmexHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    return 'a'

  @staticmethod
  def get_topic_string(product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    return [
        'bitmex_ws_orderBookL2_a%s' % worker_id,
        'bitmex_ws_trade_a%s' % worker_id,
        'bitmex_ws_instrument_%s' % worker_id,
        'bitmex_ws_liquidation_%s' % worker_id
    ]

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    self._flow_sub = flow_sub
    self._multiple_handler = None

    flow_sub.subscribe('bitmex_ws_orderBookL2_*%s' % worker_id,
                       self._feed_handler.on_ws_orderbook_l2)
    flow_sub.subscribe('bitmex_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)
    flow_sub.subscribe('bitmex_ws_instrument_*%s' % worker_id, self._feed_handler.on_ws_instrument)
    flow_sub.subscribe('bitmex_ws_liquidation_*%s' % worker_id,
                       self._feed_handler.on_ws_liquidation)

  def subscribe_book(self, product, callback):
    product = BitmexFuturesProduct.FromProduct(product)
    native_symbol = product.native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    product = BitmexFuturesProduct.FromProduct(product)
    native_symbol = product.native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_instrument(self, product, callback):
    product = BitmexFuturesProduct.FromProduct(product)
    native_symbol = product.native_symbol
    self._feed_handler.subscribe_instrument(native_symbol, callback)

  def subscribe_aggregated_book(self, product, aggregated_ticksizes, callback, worker_id='1'):
    if self._multiple_handler is None:
      self._multiple_handler = BitmexMultipleBookHandler(aggregated_ticksizes)
      self._flow_sub.subscribe('bitmex_ws_orderBookL2_*%s' % worker_id,
                               self._multiple_handler.on_ws_orderbook_l2)

    native_symbol = BitmexFuturesProduct.FromProduct(product).native_symbol
    self._multiple_handler.subscribe(native_symbol, callback)


# ----------------------------
# Huobi
# ----------------------------
class HuobiFlowBookBuilder(FlowBookBuilderBase):
  ProductType = HuobiProduct
  FeedHandlerType = HuobiHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = HuobiProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in huobi_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'huobi_ws_depth_%s%s' % (g, worker_id),
          'huobi_ws_trade_%s%s' % (g, worker_id),
          'huobi_ws_delta_depth_%s%s' % (g, worker_id),
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('huobi_ws_depth_*%s' % worker_id, self._feed_handler.on_ws_depth)
    flow_sub.subscribe('huobi_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)
    flow_sub.subscribe('huobi_ws_delta_depth_*%s' % worker_id, self._feed_handler.on_ws_diff)


# ----------------------------
# Huobi Futures
# ----------------------------
class HuobiFuturesFlowBookBuilder(FlowBookBuilderBase):
  ProductType = HuobiFuturesProduct
  FeedHandlerType = HuobiFuturesHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormFuturesProduct):
    assert isinstance(product, HuobiFuturesProduct)
    native_symbol = product.native_subscription_symbol
    for group, native_symbol_list in huobi_config.futures_group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'huobi_ws_futures_depth_%s%s' % (g, worker_id),
          'huobi_ws_futures_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None, book_builder_opts=None):
    super().__init__(feed_checker_config=feed_checker_config, book_builder_opts=book_builder_opts)
    flow_sub.subscribe('huobi_ws_futures_depth_*%s' % worker_id, self._feed_handler.on_depth)
    flow_sub.subscribe('huobi_ws_futures_trade_*%s' % worker_id, self._feed_handler.on_trade)

  def subscribe_book(self, product, callback):
    assert isinstance(product, HuobiFuturesProduct)

    native_symbol = product.native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    assert isinstance(product, HuobiFuturesProduct)

    native_symbol = product.native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# OKEX
# ----------------------------
class OkexFlowBookBuilder(FlowBookBuilderBase):
  ProductType = OkexProduct
  FeedHandlerType = OkexHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = OkexProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in okex_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'okex_ws_spot_depth_fullbook_%s%s' % (g, worker_id),
          'okex_ws_spot_depth_diff_%s%s' % (g, worker_id),
          'okex_ws_spot_deals_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('okex_ws_spot_depth_fullbook_*%s' % worker_id,
                       self._feed_handler.on_first_book)
    flow_sub.subscribe('okex_ws_spot_depth_diff_*%s' % worker_id, self._feed_handler.on_diff)
    flow_sub.subscribe('okex_ws_spot_deals_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# OKEX v3
# ----------------------------
class OkexV3FlowBookBuilder(FlowBookBuilderBase):
  ProductType = OkexProduct
  FeedHandlerType = OkexV3Handler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    # group_symbol is in v1 format.
    native_symbol = OkexProduct.FromProduct(product).native_symbol_v1
    for group, native_symbol_list in okex_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'okex_ws_spot_v3_depth_fullbook_%s%s' % (g, worker_id),
          'okex_ws_spot_v3_depth_diff_%s%s' % (g, worker_id),
          'okex_ws_spot_v3_trade_%s%s' % (g, worker_id),
          # coin 2
          'okex_feed_v3_%s%s' % (g, worker_id),
          'spot_okex_v3_%s%s' % (g, worker_id),
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('okex_ws_spot_v3_depth_fullbook_*%s' % worker_id,
                       self._feed_handler.on_first_book)
    flow_sub.subscribe('okex_ws_spot_v3_depth_diff_*%s' % worker_id, self._feed_handler.on_diff)
    flow_sub.subscribe('okex_ws_spot_v3_trade_*%s' % worker_id, self._feed_handler.on_trade)
    # coin 2
    flow_sub.subscribe('okex_feed_v3_*%s' % worker_id, self._feed_handler.on_coin2)
    flow_sub.subscribe('spot_okex_v3_*%s' % worker_id, self._feed_handler.on_coin2)

  def subscribe_book(self, product, callback):
    native_symbol = self.__class__.ProductType.FromProduct(product).native_symbol_v3
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    native_symbol = self.__class__.ProductType.FromProduct(product).native_symbol_v3
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# OKEX Futures
# ----------------------------
class OkexFuturesFlowBookBuilder(FlowBookBuilderBase):
  ProductType = OkexFuturesProduct
  FeedHandlerType = OkexFuturesHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormFuturesProduct):
    if isinstance(product, OkexFuturesProduct):
      native_symbol = product.native_subscription_symbol
    else:
      native_symbol = \
          OkexFuturesProduct.FromProduct(product).native_subscription_symbol
    for group, native_symbol_list in okex_config.futures_group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError()

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'okex_ws_futureusd_depth_fullbook_%s%s' % (g, worker_id),
          'okex_ws_futureusd_depth_diff_%s%s' % (g, worker_id),
          'okex_ws_futureusd_trade_%s%s' % (g, worker_id),
          'okex_ws_futureusd_ticker_%s%s' % (g, worker_id),
          'okex_ws_futureusd_index_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None, check_limit_price=False):
    self._feed_handler = self.FeedHandlerType(feed_checker_config=feed_checker_config,
                                              check_limit_price=check_limit_price)

    self._flow_sub = flow_sub
    flow_sub.subscribe('okex_ws_futureusd_depth_fullbook_*%s' % worker_id,
                       self._feed_handler.on_first_book)
    flow_sub.subscribe('okex_ws_futureusd_depth_diff_*%s' % worker_id, self._feed_handler.on_diff)
    flow_sub.subscribe('okex_ws_futureusd_trade_*%s' % worker_id, self._feed_handler.on_trade)
    flow_sub.subscribe('okex_ws_futureusd_ticker_*%s' % worker_id, self._feed_handler.on_ticker)
    flow_sub.subscribe('okex_ws_futureusd_index_*%s' % worker_id, self._feed_handler.on_index)

  def subscribe_book(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_ticker(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol
    self._feed_handler.subscribe_ticker(native_symbol, callback)

  def subscribe_index(self, product, callback):
    if isinstance(product, OkexFuturesProduct):
      product = OkexProduct(product.base, product.quote)
    if not isinstance(product, OkexProduct):
      product = OkexProduct.FromProduct(product)

    native_symbol = product.native_symbol
    self._feed_handler.subscribe_index(native_symbol, callback)


# ----------------------------
# OKEX Futures v3
# ----------------------------
class OkexFuturesV3FlowBookBuilder(FlowBookBuilderBase):
  ProductType = OkexFuturesProduct
  FeedHandlerType = OkexFuturesV3Handler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormFuturesProduct):
    # group_symbol is in v1 (USD quote) and v3 (USDT quote) format.
    if isinstance(product, OkexFuturesProduct):
      native_symbol = product.native_subscription_symbol
      symbol = product.subscription_symbol
    else:
      native_symbol = \
          OkexFuturesProduct.FromProduct(product).native_subscription_symbol
      symbol = OkexFuturesProduct.FromProduct(product).subscription_symbol
    for group, native_symbol_list in okex_config.futures_group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    for group, symbol_list in okex_config.futures_group_symbol.items():
      if symbol in symbol_list:
        return group
    raise ValueError()

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'okex_ws_futures_v3_depth_fullbook_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_depth_diff_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_trade_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_ticker_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_price_range_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_mark_price_%s%s' % (g, worker_id),
          'okex_rest_futures_v3_open_interest_%s%s' % (g, worker_id),
          # coin 2
          'okex_futures_feed_v3_%s%s' % (g, worker_id),
          'futures_okex_v3_%s%s' % (g, worker_id),
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None, check_limit_price=False):
    self._feed_handler = self.FeedHandlerType(feed_checker_config=feed_checker_config,
                                              check_limit_price=check_limit_price)

    self._flow_sub = flow_sub
    flow_sub.subscribe('okex_ws_futures_v3_depth_fullbook_*%s' % worker_id,
                       self._feed_handler.on_first_book)
    flow_sub.subscribe('okex_ws_futures_v3_depth_diff_*%s' % worker_id, self._feed_handler.on_diff)
    flow_sub.subscribe('okex_ws_futures_v3_trade_*%s' % worker_id, self._feed_handler.on_trade)
    flow_sub.subscribe('okex_ws_futures_v3_ticker_*%s' % worker_id, self._feed_handler.on_ticker)
    flow_sub.subscribe('okex_ws_futures_v3_price_range_*%s' % worker_id,
                       self._feed_handler.on_price_range)
    flow_sub.subscribe('okex_ws_futures_v3_mark_price_*%s' % worker_id,
                       self._feed_handler.on_mark_price)
    flow_sub.subscribe('okex_rest_futures_v3_open_interest_*%s' % worker_id,
                       self._feed_handler.on_open_interest)
    # coin 2
    flow_sub.subscribe('okex_futures_feed_v3_*%s' % worker_id, self._feed_handler.on_coin2)
    flow_sub.subscribe('futures_okex_v3_*%s' % worker_id, self._feed_handler.on_coin2)

  def subscribe_book(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_ticker(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_event('ticker', native_symbol, callback)

  def subscribe_instrument(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_event('instrument', native_symbol, callback)


# ----------------------------
# OKEX Futures v3 realtime
# ----------------------------
class OkexFuturesV3RealtimeFlowBookBuilder(FlowBookBuilderBase):
  ProductType = OkexFuturesProduct
  FeedHandlerType = OkexFuturesV3Handler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormFuturesProduct):
    # group_symbol is in v1 (USD quote) and v3 (USDT quote) format.
    if isinstance(product, OkexFuturesProduct):
      native_symbol = product.native_subscription_symbol
      symbol = product.subscription_symbol
    else:
      native_symbol = \
        OkexFuturesProduct.FromProduct(product).native_subscription_symbol
      symbol = OkexFuturesProduct.FromProduct(product).subscription_symbol
    for group, native_symbol_list in okex_config.futures_group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    for group, symbol_list in okex_config.futures_group_symbol.items():
      if symbol in symbol_list:
        return group
    raise ValueError()

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'okex_ws_futures_v3_realtime_depth_fullbook_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_realtime_depth_diff_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_trade_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_ticker_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_price_range_%s%s' % (g, worker_id),
          'okex_ws_futures_v3_mark_price_%s%s' % (g, worker_id),
          'okex_rest_futures_v3_open_interest_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None, check_limit_price=False):
    self._feed_handler = self.FeedHandlerType(feed_checker_config=feed_checker_config,
                                              check_limit_price=check_limit_price)

    self._flow_sub = flow_sub
    flow_sub.subscribe('okex_ws_futures_v3_realtime_depth_fullbook_*%s' % worker_id,
                       self._feed_handler.on_first_book)
    flow_sub.subscribe('okex_ws_futures_v3_realtime_depth_diff_*%s' % worker_id,
                       self._feed_handler.on_diff)
    flow_sub.subscribe('okex_ws_futures_v3_trade_*%s' % worker_id, self._feed_handler.on_trade)
    flow_sub.subscribe('okex_ws_futures_v3_ticker_*%s' % worker_id, self._feed_handler.on_ticker)
    flow_sub.subscribe('okex_ws_futures_v3_price_range_*%s' % worker_id,
                       self._feed_handler.on_price_range)
    flow_sub.subscribe('okex_ws_futures_v3_mark_price_*%s' % worker_id,
                       self._feed_handler.on_mark_price)
    flow_sub.subscribe('okex_rest_futures_v3_open_interest_*%s' % worker_id,
                       self._feed_handler.on_open_interest)

  def subscribe_book(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_ticker(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_event('ticker', native_symbol, callback)

  def subscribe_instrument(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_event('instrument', native_symbol, callback)


# ----------------------------
# OKEX Futures Swap
# ----------------------------
class OkexFuturesSwapFlowBookBuilder(FlowBookBuilderBase):
  ProductType = OkexFuturesProduct
  FeedHandlerType = OkexFuturesSwapHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormFuturesProduct):
    if isinstance(product, OkexFuturesProduct):
      native_symbol = product.native_symbol_v3
    else:
      native_symbol = \
          OkexFuturesProduct.FromProduct(product).product.native_symbol_v3
    for group, native_symbol_list in okex_config.swap_group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError()

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'okex_ws_swap_depth_fullbook_%s%s' % (g, worker_id),
          'okex_ws_swap_depth_diff_%s%s' % (g, worker_id),
          'okex_ws_swap_trade_%s%s' % (g, worker_id),
          'okex_ws_swap_ticker_%s%s' % (g, worker_id),
          'okex_ws_swap_funding_rate_%s%s' % (g, worker_id),
          'okex_ws_swap_mark_price_%s%s' % (g, worker_id),
          'okex_rest_swap_open_interest_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    self._flow_sub = flow_sub
    flow_sub.subscribe('okex_ws_swap_depth_fullbook_*%s' % worker_id,
                       self._feed_handler.on_first_book)
    flow_sub.subscribe('okex_ws_swap_depth_diff_*%s' % worker_id, self._feed_handler.on_diff)
    flow_sub.subscribe('okex_ws_swap_trade_*%s' % worker_id, self._feed_handler.on_trade)
    flow_sub.subscribe('okex_ws_swap_ticker_*%s' % worker_id, self._feed_handler.on_ticker)
    flow_sub.subscribe('okex_ws_swap_funding_rate_*%s' % worker_id,
                       self._feed_handler.on_funding_rate)
    flow_sub.subscribe('okex_ws_swap_mark_price_*%s' % worker_id, self._feed_handler.on_mark_price)
    flow_sub.subscribe('okex_rest_swap_open_interest_*%s' % worker_id,
                       self._feed_handler.on_open_interest)

  def subscribe_book(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_ticker(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_event('ticker', native_symbol, callback)

  def subscribe_instrument(self, product, callback):
    if not isinstance(product, OkexFuturesProduct):
      product = OkexFuturesProduct.FromProduct(product)

    native_symbol = product.native_symbol_v3
    self._feed_handler.subscribe_event('instrument', native_symbol, callback)


# ----------------------------
# GDAX
# ----------------------------
class GdaxFlowBookBuilder(FlowBookBuilderBase):
  ProductType = GdaxProduct
  FeedHandlerType = GdaxHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = GdaxProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in gdax_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['gdax_ws_level2_%s%s' % (g, worker_id), 'gdax_ws_full_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('gdax_ws_level2_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('gdax_ws_full_*%s' % worker_id, self._feed_handler.on_full)


# ----------------------------
# BITHUMB
# ----------------------------
class BithumbFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BithumbProduct
  FeedHandlerType = BithumbHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BithumbProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in bithumb_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['bithumb_ws_depth_%s%s' % (g, worker_id), 'bithumb_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('bithumb_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('bithumb_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)

  def subscribe_book(self, product, callback):
    native_symbol = BithumbProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    native_symbol = BithumbProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# BITHUMB v2
# ----------------------------
class BithumbV2FlowBookBuilder(FlowBookBuilderBase):
  ProductType = BithumbProduct
  FeedHandlerType = BithumbV2Handler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BithumbProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in bithumb_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['bithumb_ws_depth_%s%s' % (g, worker_id), 'bithumb_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('bithumb_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('bithumb_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# BITHUMB v3
# ----------------------------
class BithumbV3FlowBookBuilder(FlowBookBuilderBase):
  ProductType = BithumbProduct
  FeedHandlerType = BithumbV3Handler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BithumbProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in bithumb_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['bithumb_ws_depth_%s%s' % (g, worker_id), 'bithumb_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('bithumb_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('bithumb_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# Upbit
# ----------------------------
class UpbitFlowBookBuilder(FlowBookBuilderBase):
  ProductType = UpbitProduct
  FeedHandlerType = UpbitHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = UpbitProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in upbit_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['upbit_ws_depth_%s%s' % (g, worker_id), 'upbit_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('upbit_ws_depth_*%s' % worker_id, self._feed_handler.on_ws_book)
    flow_sub.subscribe('upbit_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)

  def subscribe_book(self, product, callback):
    native_symbol = UpbitProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    native_symbol = UpbitProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# Kraken
# ----------------------------
class KrakenFlowBookBuilderBase(FlowBookBuilderBase):
  ProductType = KrakenProduct
  FeedHandlerType = None
  FeedSource = None

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = KrakenProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in kraken_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'kraken_%s_depth_%s%s' % (cls.FeedSource, g, worker_id),
          'kraken_%s_trade_%s%s' % (cls.FeedSource, g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('kraken_%s_depth_*%s' % (self.FeedSource, worker_id),
                       self._feed_handler.on_depth)
    flow_sub.subscribe('kraken_%s_trade_*%s' % (self.FeedSource, worker_id),
                       self._feed_handler.on_trade)


class KrakenRestFlowBookBuilder(KrakenFlowBookBuilderBase):
  FeedHandlerType = KrakenRestHandler
  FeedSource = 'rest'
  support_book_builder_observer = True


class KrakenWsFlowBookBuilder(KrakenFlowBookBuilderBase):
  FeedHandlerType = KrakenWsHandler
  FeedSource = 'ws'
  support_book_builder_observer = True


# ----------------------------
# Kraken
# ----------------------------
class KrakenFuturesFlowBookBuilder(FlowBookBuilderBase):
  ProductType = KrakenFuturesProduct
  FeedHandlerType = KrakenFuturesHandler
  FeedSource = 'ws'
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = KrakenFuturesProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in kraken_futures_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'kraken_futures_%s_depth_%s%s' % (cls.FeedSource, g, worker_id),
          'kraken_futures_%s_trade_%s%s' % (cls.FeedSource, g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('kraken_futures_%s_depth_*%s' % (self.FeedSource, worker_id),
                       self._feed_handler.on_depth)
    flow_sub.subscribe('kraken_futures_%s_trade_*%s' % (self.FeedSource, worker_id),
                       self._feed_handler.on_trade)


# ----------------------------
# Fcoin
# ----------------------------
class FcoinFlowBookBuilder(FlowBookBuilderBase):
  ProductType = FcoinProduct
  FeedHandlerType = FcoinHandler

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = FcoinProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in fcoin_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['fcoin_ws_depth_%s%s' % (g, worker_id), 'fcoin_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('fcoin_ws_depth_*%s' % worker_id, self._feed_handler.on_ws_depth)
    flow_sub.subscribe('fcoin_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Quoinex
# ----------------------------
class QuoinexFlowBookBuilder(FlowBookBuilderBase):
  ProductType = QuoinexProduct
  FeedHandlerType = QuoinexHandler

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = QuoinexProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in quoinex_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['quoinex_ws_depth_%s%s' % (g, worker_id), 'quoinex_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('quoinex_ws_depth_*%s' % worker_id, self._feed_handler.on_ws_depth)
    flow_sub.subscribe('quoinex_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Quoinex single channel
# ----------------------------
class QuoinexSingleChannelFlowBookBuilder(FlowBookBuilderBase):
  ProductType = QuoinexProduct
  FeedHandlerType = QuoinexHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = QuoinexProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in quoinex_config.single_channel_group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'quoinex_ws_single_channel_depth_%s%s' % (g, worker_id),
          'quoinex_ws_single_channel_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('quoinex_ws_single_channel_depth_*%s' % worker_id,
                       self._feed_handler.on_ws_depth_single_channel)
    flow_sub.subscribe('quoinex_ws_single_channel_trade_*%s' % worker_id,
                       self._feed_handler.on_ws_trade)


# ----------------------------
# Hitbtc
# ----------------------------
class HitbtcFlowBookBuilder(FlowBookBuilderBase):
  ProductType = HitbtcProduct
  FeedHandlerType = HitbtcHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = HitbtcProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in hitbtc_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['hitbtc_ws_depth_%s%s' % (g, worker_id), 'hitbtc_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('hitbtc_ws_depth_*%s' % worker_id, self._feed_handler.on_ws_depth)
    flow_sub.subscribe('hitbtc_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Bequant
# ----------------------------
class BequantFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BequantProduct
  FeedHandlerType = BequantHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BequantProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in hitbtc_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['bequant_ws_depth_%s%s' % (g, worker_id), 'bequant_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('bequant_ws_depth_*%s' % worker_id, self._feed_handler.on_ws_depth)
    flow_sub.subscribe('bequant_ws_trade_*%s' % worker_id, self._feed_handler.on_ws_trade)


# ----------------------------
# Bitstamp
# ----------------------------
class BitstampFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BitstampProduct
  FeedHandlerType = BitstampHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = BitstampProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in bitstamp_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(native_symbol)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'bitstamp_ws_depth_%s%s' % (g, worker_id), 'bitstamp_ws_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('bitstamp_ws_depth_*%s' % worker_id, self._feed_handler.on_depth)
    flow_sub.subscribe('bitstamp_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# Korbit Rest
# ----------------------------
class KorbitRestFlowBookBuilder(FlowBookBuilderBase):
  ProductType = KorbitProduct
  FeedHandlerType = KorbitRestHandler

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = KorbitProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in korbit_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'korbit_rest_depth_%s%s' % (g, worker_id), 'korbit_rest_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('korbit_rest_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('korbit_rest_trade_*%s' % worker_id, self._feed_handler.on_trade)

  def subscribe_book(self, product, callback):
    native_symbol = KorbitProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    native_symbol = KorbitProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# Korbit Ws
# ----------------------------
class KorbitWsFlowBookBuilder(FlowBookBuilderBase):
  ProductType = KorbitProduct
  FeedHandlerType = KorbitWsHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = KorbitProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in korbit_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['korbit_ws_depth_%s%s' % (g, worker_id), 'korbit_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('korbit_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('korbit_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# Coinone Rest
# ----------------------------
class CoinoneRestFlowBookBuilder(FlowBookBuilderBase):
  ProductType = CoinoneProduct
  FeedHandlerType = CoinoneRestHandler

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = CoinoneProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in coinone_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += [
          'coinone_rest_depth_%s%s' % (g, worker_id), 'coinone_rest_trade_%s%s' % (g, worker_id)
      ]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('coinone_rest_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('coinone_rest_trade_*%s' % worker_id, self._feed_handler.on_trade)

  def subscribe_book(self, product, callback):
    native_symbol = CoinoneProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    native_symbol = CoinoneProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# Coinone Ws
# ----------------------------
class CoinoneWsFlowBookBuilder(FlowBookBuilderBase):
  ProductType = CoinoneProduct
  FeedHandlerType = CoinoneHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = CoinoneProduct.FromProduct(product).native_subscription_symbol
    for group, native_symbol_list in coinone_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['coinone_ws_depth_%s%s' % (g, worker_id), 'coinone_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('coinone_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('coinone_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)

  def subscribe_book(self, product, callback):
    native_symbol = CoinoneProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe(native_symbol, callback)
    self._feed_handler.add_check_product(product)

  def subscribe_trade(self, product, callback):
    native_symbol = CoinoneProduct.FromProduct(product).native_symbol
    self._feed_handler.subscribe_trade(native_symbol, callback)
    self._feed_handler.add_check_product(product)


# ----------------------------
# Deribit
# ----------------------------
class DeribitFlowBookBuilder(FlowBookBuilderBase):
  ProductType = DeribitFuturesProduct
  FeedHandlerType = DeribitHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    product = DeribitFuturesProduct.FromProduct(product)
    for group, symbol_list in deribit_config.group_symbol.items():
      product_list = [DeribitFuturesProduct.FromStrProduct(symbol) for symbol in symbol_list]
      if product in product_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['deribit_ws_depth_%s%s' % (g, worker_id), 'deribit_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('deribit_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('deribit_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# Gopax
# ----------------------------
class GopaxFlowBookBuilder(FlowBookBuilderBase):
  ProductType = GopaxProduct
  FeedHandlerType = GopaxHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    native_symbol = GopaxProduct.FromProduct(product).native_symbol
    for group, native_symbol_list in gopax_config.group_symbol.items():
      if native_symbol in native_symbol_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['gopax_ws_depth_%s%s' % (g, worker_id), 'gopax_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('gopax_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('gopax_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# Bybit
# ----------------------------
class BybitFlowBookBuilder(FlowBookBuilderBase):
  ProductType = BybitFuturesProduct
  FeedHandlerType = BybitHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    product = BybitFuturesProduct.FromProduct(product)
    for group, symbol_list in bybit_config.group_symbol.items():
      product_list = [BybitFuturesProduct.FromStrProduct(symbol) for symbol in symbol_list]
      if product in product_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['bybit_ws_depth_%s%s' % (g, worker_id), 'bybit_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('bybit_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('bybit_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# UniswapV3
# ----------------------------
class UniswapV3FlowBookBuilder(FlowBookBuilderBase):
  ProductType = PyProduct
  FeedHandlerType = UniswapHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    product = UniswapProduct.FromProduct(product)
    for group, symbol_list in uniswapv3_config.group_symbol.items():
      product_list = [UniswapProduct.FromStrProduct(symbol) for symbol in symbol_list]
      if product in product_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['uniswapv3_rest_depth_%s%s' % (g, worker_id), 'uniswapv3_rest_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('uniswapv3_rest_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('uniswapv3_rest_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# UniswapV3Arbitrum
# ----------------------------
class UniswapV3ArbitrumFlowBookBuilder(FlowBookBuilderBase):
  ProductType = PyProduct
  FeedHandlerType = UniswapHandler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    product = UniswapProduct.FromProduct(product)
    for group, symbol_list in uniswapv3_arbitrum_config.group_symbol.items():
      product_list = [UniswapProduct.FromStrProduct(symbol) for symbol in symbol_list]
      if product in product_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['uniswapv3-arbitrum_ws_depth_%s%s' % (g, worker_id), 'uniswapv3-arbitrum_ws_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('uniswapv3-arbitrum_ws_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('uniswapv3-arbitrum_ws_trade_*%s' % worker_id, self._feed_handler.on_trade)


# ----------------------------
# UniswapV2
# ----------------------------
class UniswapV2FlowBookBuilder(FlowBookBuilderBase):
  ProductType = PyProduct
  FeedHandlerType = UniswapV2Handler
  support_book_builder_observer = True

  @staticmethod
  def find_feed_group(product: NormProduct):
    product = UniswapV2Product.FromProduct(product)
    for group, symbol_list in uniswapv2_config.group_symbol.items():
      product_list = [UniswapV2Product.FromStrProduct(symbol) for symbol in symbol_list]
      if product in product_list:
        return group
    raise ValueError(product)

  @classmethod
  def get_topic_string(cls, product: Union[NormProduct, List[NormProduct], None], worker_id='1'):
    groups = _get_feed_group(product, cls.find_feed_group)
    topics = []
    for g in groups:
      topics += ['uniswapv2_rest_depth_%s%s' % (g, worker_id), 'uniswapv2_rest_trade_%s%s' % (g, worker_id)]
    return topics

  def __init__(self, flow_sub, worker_id='1', feed_checker_config=None):
    super().__init__(feed_checker_config=feed_checker_config)
    flow_sub.subscribe('uniswapv2_rest_depth_*%s' % worker_id, self._feed_handler.on_book)
    flow_sub.subscribe('uniswapv2_rest_trade_*%s' % worker_id, self._feed_handler.on_trade)
